package slidingpenguins.slideshow.model {
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;
	
	import mx.collections.ArrayList;
	import mx.collections.IList;
	import mx.utils.StringUtil;
	
	import slidingpenguins.utils.ListTypeRestricter;

	[Bindable]
	public class Slideshow {
		private var _slides:IList;
		private var _aspectRatio:AspectRatio;
		private var _lastInlineImageID:uint;

		public function Slideshow() {
			_slides = new ListTypeRestricter(new ArrayList(), Slide);
			_aspectRatio = new AspectRatio(16, 9);
			_lastInlineImageID = 0;
		}

		public function get slides():IList {
			return _slides;
		}

		public function get aspectRatio():AspectRatio {
			return _aspectRatio;
		}
		
		public function set aspectRatio(aspectRatio:AspectRatio):void {
			_aspectRatio = aspectRatio;
		}
		
		public function get lastInlineImageID():uint {
			return _lastInlineImageID;
		}
		
		public function incrementLastInlineImageID():void {
			_lastInlineImageID++;
		}
		
		public static function parseDistance(distance:String):Distance {
			if (distance == "auto") {
				return new Distance(0, Distance.DISTANCE_UNIT_AUTO);
			} else if (distance.match(/^-?[1-9]\d*$/).length > 0) {
				return new Distance(distance as Number, Distance.DISTANCE_UNIT_PIXELS);
			} else if (distance.match(/^-?[1-9]\d*(\.\d*)?%$/).length > 0) {
				return new Distance(distance.substr(0, distance.length - 1) as Number, Distance.DISTANCE_UNIT_PERCENTAGE);
			} else {
				throw new TypeError();
			}
		}
		
		public static function parseNonNegativeNumber(number:String):uint {
			if (isRotationSyntaxValid(number)) {
				return number as uint;
			} else {
				throw new TypeError();
			}
		}

		public static function parseAspectRation(aspectRatio:String):AspectRatio {
			if (isAspectRationSyntaxValid(aspectRatio)) {
				var components:Array = aspectRatio.split(":");
				
				return new AspectRatio(components[0] as uint, components[1] as uint);
			} else {
				throw new TypeError();
			}
		}
		
		public static function isDistanceSyntaxValid(distance:String):Boolean {
			return distance == "auto" ||
				distance.match(/^-?[1-9]\d*$/).length > 0 ||
				distance.match(/^-?[1-9]\d*(\.\d*)?%$/).length > 0;
		}

		public static function isPositiveDistanceSyntaxValid(distance:String):Boolean {
			return isPositiveDistanceSyntaxValid(distance) && distance.match(/^[^\-]*$/).length > 0;
		}

		public static function isRotationSyntaxValid(rotation:String):Boolean {
			return rotation.match(/^\d+$/).length > 0 &&
				(rotation as uint) >= 0 &&
				(rotation as uint) < 360;
		}

		public static function isNonNegativeNumberSyntaxValid(zIndex:String):Boolean {
			return zIndex.match(/^\d+$/).length > 0 &&
				(zIndex as uint) >= 0;
		}
		
		public static function isAspectRationSyntaxValid(aspectRation:String):Boolean {
			return aspectRation.match(/^[1-9]\d*:[1-9]\d*$/).length > 0;
		}

		public static function isSlideshowXMLValid(slideshowInXML:XML):Boolean {
			// no validation against XML Schema in Flex, so some ugly validation
			try {
				if (slideshowInXML.localName() != "slideshow") {
					return false;
				}
				
				if (slideshowInXML.attribute("aspectRatio").length() > 0 &&
					!isAspectRationSyntaxValid(slideshowInXML.@aspectRatio)) {
					return false;
				}
				
				if (slideshowInXML.attribute("lastInlineImageID").length() == 0 ||
					!isNonNegativeNumberSyntaxValid(slideshowInXML.@lastInlineImageID)) {
					return false;
				}

				for each (var slide:XML in slideshowInXML.elements()) {
					if (slide.localName() != "slide") {
						return false;
					}

					for each (var image:XML in slide.elements()) {
						if (image.localName() != "image") {
							return false;
						}

						var attributeChecks:Array = [
							image.attribute("uri").length() != 0,
							image.attribute("left").length() == 0 || isDistanceSyntaxValid(image.@left),
							image.attribute("top").length() == 0 || isDistanceSyntaxValid(image.@top),
							image.attribute("width").length() == 0 || isPositiveDistanceSyntaxValid(image.@width),
							image.attribute("height").length() == 0 || isPositiveDistanceSyntaxValid(image.@height),
							image.attribute("rotation").length() == 0 || isRotationSyntaxValid(image.@rotation),
							image.attribute("zIndex").length() == 0 || isNonNegativeNumberSyntaxValid(image.@zIndex),
						];
						
						for each (var attributeCheck:Boolean in attributeChecks) {
							if (!attributeCheck) {
								return false;
							}
						}
					}
				}
			} catch (e:TypeError) {
				return false;
			}

			return true;
		}

		public static function loadFromXML(slideshowInXML:XML):Slideshow {
			if (!isSlideshowXMLValid(slideshowInXML)) {
				throw new SlideshowFileParsingError();
			}
			
			var result:Slideshow = new Slideshow();

			if (slideshowInXML.attribute("aspectRatio").length() > 0) {
				result.aspectRatio = parseAspectRation(slideshowInXML.@aspectRatio);
			}
			
			result._lastInlineImageID = parseNonNegativeNumber(slideshowInXML.@lastInlineImageID);

			for each (var slideInXML:XML in slideshowInXML.slide) {
				var slide:Slide = new Slide();
				
				for each (var imageInXML:XML in slideInXML.image) {
					var image:Image = new Image(imageInXML.@uri);
					
					if (imageInXML.attribute("inline").length() > 0) {
						image.isInline = true;
					}
					if (imageInXML.attribute("left").length() > 0) {
						image.left = parseDistance(imageInXML.@distance);
					}
					if (imageInXML.attribute("top").length() > 0) {
						image.top = parseDistance(imageInXML.@top);
					}
					if (imageInXML.attribute("width").length() > 0) {
						image.width = parseDistance(imageInXML.@width);
					}
					if (imageInXML.attribute("height").length() > 0) {
						image.height = parseDistance(imageInXML.@height);
					}
					if (imageInXML.attribute("rotation").length() > 0) {
						image.rotation = parseNonNegativeNumber(imageInXML.@rotation);
					}
					if (imageInXML.attribute("zIndex").length() > 0) {
						image.zIndex = parseNonNegativeNumber(imageInXML.@zIndex);
					}
					
					slide.images.addItem(image);
				}
				
				result.slides.addItem(slide);
			}
			
			return result;
		}
		
		public function saveToXML(stream:ByteArray):void {
			stream.writeUTFBytes('<?xml version="1.0\" encoding="UTF-8"?>');
			stream.writeUTFBytes(StringUtil.substitute(
				'<slideshow xmlns="http://www.example.org/Slideshow" aspectRation="{0}" lastInlineImageID="{1}">',
				aspectRatio.toString(),
				lastInlineImageID
			));

			for each (var slide:Slide in slides) {
				stream.writeUTFBytes("<slide>");
				
				for each (var image:Image in slide.images) {
					stream.writeUTFBytes(StringUtil.substitute(
						'<image uri=\"{0}\"' +
						'{1}' +
						' left="{2}"' +
						' top="{3}"' +
						' width="{4}"' +
						' height="{5}"' +
						' rotation="{6}"' +
						' zIndex="{7}"' +
						'/>',
						image.uri,
						(image.isInline ? " inline=\"inline\"" : ""),
						image.left.toString(),
						image.top.toString(),
						image.width.toString(),
						image.height.toString(),
						image.rotation,
						image.zIndex
					));
				}
				
				stream.writeUTFBytes("</slide>");
			}

			stream.writeUTFBytes("</slideshow>\n");
		}
	}
}
